Repository navigation
DF-26795: apply RDD symbol overrides in coinmetrics-lwba - #5380
Open
cl-efornaciari wants to merge 24 commits into
Open
cl-efornaciari wants to merge 24 commits into
cl-efornaciari wants to merge 24 commits into
Conversation
…pped The adapter clones the endpoint exported by the coinmetrics package with Object.assign, which copies `requestTransforms` by reference. `symbolOverrider` stays bound to the original endpoint, which is never initialize()d, so `this.adapterName` reads as undefined, the override lookup misses, and every RDD symbol override is silently discarded. Give the clone its own `aliases` and `requestTransforms` arrays and rebind `symbolOverrider` to it. Affects two live feeds: - FXS/USD (mainnet, 0xc1c54D09554Cf65d74a289805c2731cf6EA57373) subscribed to `frax` instead of `frax_frax`. CoinMetrics prices `frax` off second-tier venues and publishes it only ~41% of the time, so ~65% of requests 504. - MATIC/USD (sepolia-arbitrum, 0xE6616a09e029D477183CbA6BB6133643b522C15c) subscribed to `matic` instead of `pol`. CoinMetrics has not published `matic` in 7 days, so it fails 100%. DF-26795 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: fbfdf0f The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
CI requires changed adapters to pin the latest ea-framework. Bumping only coinmetrics-lwba is not viable: it imports its endpoint from the coinmetrics package, so a split pin loads two framework copies. Adapter.initialize (2.19.1) then calls endpoint.initialize (2.17.1), whose logger factory singleton was never set, and startup fails with "The logger factory provider does not have a factory set". Both packages therefore move together. 2.19.1 adds LwbaEndpoint.resultValidator, which rejects invariant-violating quotes at cache-write time instead of throwing on the response path. Behaviour for a violating quote changes from a 500 AdapterLWBAError to a 200 carrying the last known-good quote, since the bad quote no longer reaches the cache. Two snapshots are updated to record this. DF-26795 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
token-allocation aggregates its v3 adapters into a single PriceAdapter<any>[], so every member must be built against the same framework copy. Bumping coinmetrics alone broke nominal type identity for the array: Type 'PriceAdapterRequest<...framework-npm-2.19.1...>' is not assignable to Type 'PriceAdapterRequest<...framework-npm-2.17.1...>' So token-allocation and the remaining ten members move to 2.19.1 together: blocksize-capital, cfbenchmarks, coingecko, coinmarketcap, coinpaprika, cryptocompare, finage, kaiko, ncfx, tiingo. 2.19.1 adds LwbaEndpoint.resultValidator, which rejects invariant-violating quotes at cache-write time rather than throwing on the read path. A violating quote now yields 502, or 200 with the last known-good quote where one was already cached, instead of 500. Four snapshots and one hardcoded status assertion in cfbenchmarks are updated to record this. This path has never fired in production: zero 500s across ~68M requests over 7 days on both coinmetrics adapters. DF-26795 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cl-efornaciari
force-pushed
the
feature/DF-26795/coinmetrics-lwba-request-overrides
branch
from
September 15, 2026 21:32
ae62e1f to
dc45be9
Compare
cl-efornaciari
commented
Sep 29, 2026
cl-efornaciari
commented
Sep 29, 2026
…cation v3 set coinmetrics-lwba imports its endpoint from coinmetrics, so both must pin the same framework copy; token-allocation aggregates its v3 adapters into a single PriceAdapter<any>[], so all members move together for nominal type identity: blocksize-capital, cfbenchmarks, coingecko, coinmarketcap, coinpaprika, cryptocompare, finage, kaiko, ncfx, tiingo. 2.19.1's LwbaEndpoint.resultValidator rejects invariant-violating quotes at cache-write time: six snapshots move from a read-path 500 to a 502, or a 200 with the last known-good quote where one is cached. cfbenchmarks' hardcoded 500 assertion becomes 502. The chaining added in 2.19.1 also lets coinmetrics-lwba pass customOutputValidation as a constructor param again. tiingo's stock_quotes unit test stubs adapterSettings, so it needs the new opt-in MAX_WS_CONNECTION_AGE_SECONDS key (2.20.0, #901), matching the framework's generator template fix. DF-26795
alejoberardino
approved these changes
Oct 1, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The
coinmetrics-lwbaadapter silently discards every RDD symbol override. It has done so since the clone hack was introduced, so RDD has been correct while the adapter ignored it.packages/sources/coinmetrics-lwba/src/index.tsbuilds its endpoint by cloning the one exported from thecoinmetricspackage:Object.assigncopiesrequestTransformsby reference.symbolOverriderwas bound to the original endpoint in theAdapterEndpointconstructor, but only the clone is everinitialize()d — so the inherited binding readsthis.adapterNameasundefined, the override lookup misses, and the override is dropped with no error.Only request overrides break. Hardcoded endpoint overrides and
adapterNameOverridedon't depend onthis.adapterName, so they still work — which is part of why this stayed invisible.Impact
A full scan of reference-data-directory finds 2 live feeds affected — the only live feeds carrying a semantically meaningful
coinmetrics-lwbaoverride:0xc1c54D09554Cf65d74a289805c2731cf6EA57373FRAX → frax_frax0xE6616a09e029D477183CbA6BB6133643b522C15cMATIC → polNine other feeds carry an override block but are dead/inactive, and most of those are case-only no-ops (
POL → pol) that the transport'stoLowerCase()already handles.Why the wrong symbol matters
fraxandfrax_fraxare the same asset (~$0.29) but CoinMetrics prices them off disjoint venue sets:frax_frax— Binance, Bybit, Bitget, Gate.io, MEXC. Deep books, ~0.20% spread, streams continuously.frax— Poloniex, HitBTC, LBank, Huobi, Gemini, Bithumb, KuCoin.ask_sizeas low as 46, ~0.88% spread, and absent 59% of the time.cache_data_set_count_totalover 24h, where full rate is 1,382,400 writes/day:Coverage predicts failure almost 1:1.
maticis the control: same adapter, same socket — CoinMetrics has not published it once in 7 days, whilepol(sent directly by mainnet feeds, no override needed) runs at 0% failure.Changes
LwbaEndpointconstructed fromendpointParameters(now exported fromcoinmetrics/src/endpoint/lwba.ts). TheAdapterEndpointconstructor bindsrequestTransforms(symbolOverrider) to the instance it creates, and only this instance is everinitialize()d — so overrides resolve against the initialized adapter name. Freshaliases/requestTransformsarrays also end the shared-array mutation the clone relied on.result = midmapping incustomOutputValidation, passed as a constructor param: framework ≥ 2.19.1 chains it ahead of the built-in bid ≤ mid ≤ ask invariant check, which still runs when this returnsundefined. (The mapping is required on every framework version — LWBA responses typeResult: nulland nothing else populates it.)@chainlink/external-adapter-framework2.17.1 → 2.20.0 across the 13 coupled packages (details below).test/integration/adapter-ws-overrides.test.tsasserting the subscribed URL containsassets=frax_fraxand that the overridden symbol resolves a price; add a FRAX/USD request with the override block totest-payload.json.resultValidatorbehavior; add the opt-inMAX_WS_CONNECTION_AGE_SECONDSkey to tiingo's stubbed unit-test settings (2.20.0).Framework bump: 2.17.1 → 2.20.0 (13 packages)
The
Check that changed adapters have the latest EA framework versionjob requires changed adapters to pin latest.coinmetrics and coinmetrics-lwba move together. coinmetrics-lwba imports its endpoint from the coinmetrics package; a split pin loads two framework copies under PnP and startup fails (
The logger factory provider does not have a factory set) — the new copy'sstart()sets the logger factory on its own module singleton, and the old copy never receives one. Reproduces in the test suite and would fail identically in production.token-allocation's v3 adapter set moves together. It aggregates members into a single
PriceAdapter<any>[], so every member must be compiled against the same framework copy (nominal type identity):So blocksize-capital, cfbenchmarks, coingecko, coinmarketcap, coinpaprika, cryptocompare, finage, kaiko, ncfx, tiingo and token-allocation (devDependency) move with the two coinmetrics packages.
Behaviour change:
LwbaEndpoint.resultValidator(2.19.1, #876)AdapterLWBAErrorthrown on the read path (and the bad quote cached, so every request 500s until a valid one replaces it)Six snapshots and the cfbenchmarks assertion record this. This path has never fired in production — over 7 days across both adapters, zero 500s in ~68M requests:
The change is also arguably an improvement: the bad tick is dropped at the write boundary, the last good quote stands, and its original
providerIndicatedTimeUnixMsis preserved so staleness remains visible downstream.2.20.0 (#901)
Adds opt-in
MAX_WS_CONNECTION_AGE_SECONDS(proactive WS reconnect; disabled by default, no behaviour change unless configured). Only test impact: tiingo'sstock_quotesunit test stubsadapterSettingsand needs the new key, mirroring #901's own generator-template fix.Steps to Test
Automated — the new integration test captures the URL the adapter opens against the mocked WS provider and asserts on
assets=. Verified it fails for the right reason by temporarily reverting the fix:...?assets=frax&api_key=......?assets=frax_frax&api_key=...That reproduces the production symptom exactly: the mock streams
frax_frax-usd, so with the override dropped nothing ever lands under the requested cache key.Full suite across the 13 changed packages (Node 20,
METRICS_ENABLED=falseto match CI): 31 suites / 234 tests / 142 snapshots passed. Repo-wideyarn setupclean,yarn install --immutableclean, eslint 0 errors, prettier clean.Manual:
meta.metrics.feedIdis computed post-transform, so it reads"base":"frax_frax"with the fix and"base":"frax"without.Rollout note
Deploying changes the generated WS URL, so the transport closes and reopens the shared connection (websocket.ts) — a brief one-time blip across all feeds on this adapter as it resubscribes with
frax_fraxin place offrax. 2.20.0'sMAX_WS_CONNECTION_AGE_SECONDSis opt-in and unset, so it adds no new connection churn.Confirm afterwards via
cache_data_set_count_totalfor thefrax_fraxfeed_id; it should climb toward the ~1.38M/day full rate. Coverage forfrax_fraxon the asset-quotes product is currently supported by a 2-minute local sample plus full coverage on the REST reference-rate product, so the post-deploy metric is the real confirmation.Quality Assurance
infra-k8sconfiguration file. — no env var changesadapter-secretsconfiguration file. — no env var changestest-payload.jsonfile with relevant requests.🤖 Generated with Claude Code